<?php

namespace Application\System\Controller\X;

use Aoe\Database\Platform;
use Aoe\Database\SqlBuilder;
use Aoe\Emulator\Ifc\Key;
use Aoe\Emulator\Ifc\Type;
use Exception;

/**
 * # 通过中间码执行数据库的表创建与删除操作
 */
class Dms
{
    public static int $defaultTextLength = 125;

    public static string $split = "\n/*----------------*/\n";

    protected static array $column_type = [
        2  => 'TINYINT',
        4  => 'SMALLINT',
        8  => 'MEDIUMINT',
        16 => 'INT',
        32 => 'BIGINT',
    ];
    
    private Platform $platform;
    
    private function __construct() {}
    
    
    /**
     * @param SqlBuilder $builder
     * @param array                 $tables
     * @param bool                  $install
     *
     * @return void
     * @throws Exception
     */
    public static function execute(SqlBuilder $builder, array $tables, bool $install = true): void
    {
        $maker = new self();
        $maker->platform = $builder->getPlatform();
        
        $sqls = [];
        foreach ($tables as $table) {
            if(!isset($table[Key::NAME]) || !is_string($table[Key::NAME])) continue;
            
            $sqls[] = $maker->_drop_sql($table[Key::NAME]);
            if ($install) $sqls[] = $maker->_table_sql($table);
        }
        
        if (!$builder->getPlatform()->canGroup()) {
            foreach ($sqls as $sql) $builder->execute($sql, [], SqlBuilder::FETCH_CREATE);
            return;
        }
        
        $sql = join(self::$split, $sqls);
        $builder->execute($sql, [], SqlBuilder::FETCH_CREATE);
    }

    /**
     * 数据库操作
     *
     * @param array{
     *     name: string,
     *     label: string,
     *     uuid: bool,
     *     comment?: string,
     *     elements: array,
     *   } $table
     *
     * @return string
     * @throws Exception
     */
    private function _table_sql(array $table): string
    {
        $uuid = $table[Type::UUID] ?? false;
        $columns = $table[Key::ELEMENTS] ?? [];
        
        $platform = $this->platform;
        
        $cs = [];
        foreach ($columns as $column) $cs[] = '  ' . $this->_column_sql($column);
        $cs[] = $platform->useStatusSql();
        $cs[] = $platform->useAdminSql();
        $cs[] = $platform->useCTSql();
        $cs[] = $platform->useUTSql();
        
        $sql = ["CREATE TABLE `$table[name]` ("];
        $sql[] = $platform->useIdSql($uuid);
        $sql[] = join(",\n", $cs);
        $sql[] = $platform->useAfterSql($table, $uuid);

        return join("\n", $sql);
    }

    /**
     * @param array{
     *     name: string,
     *     pascal: string,
     *     label?: string,
     *     must: bool,
     *     full: bool,
     *     default?: mixed,
     *     comment?: string } $column
     *
     * @return string
     */
    private function _column_sql(array $column): string
    {
        $name = $column[Key::NAME];
        $type = $this->_type_sql($column);

        $platform = $this->platform;
        $default = isset($column[Type::DFT]) ? $platform->useDefaultValueSql($column[Type::DFT]) : '';
        $full    = isset($column[Type::NULLABLE])    ? 'NOT NULL' : '';
        $comment = $platform->useCommentSql($column);

        return "`$name` $type $default $full $comment";
    }

    /**
     * @param array{
     *     pascal: string,
     *     length?: int,
     *     double?: bool,
     *     unsigned?: bool
     *   } $column
     *
     * @return string
     */
    private function _type_sql(array $column): string
    {
        $length = $column[Type::LENGTH] ?? 0;
        $type = $column[Key::PASCAL] ?? Type::STRING;
        $int = $this->platform->useIntString();

        return match ($type) {
            Type::RELATION => ($column[Type::UUID] ?? false) ? 'CHAR(100)' : $int,
            Type::CARD => 'CHAR(18)',
            Type::TEXT => 'VARCHAR(' . ($length ?? self::$defaultTextLength) . ')',
            Type::STRING => 'CHAR(' . ($length ?: 100) . ')',
            Type::SELECT, Type::IP, Type::DATE, Type::TIME => $int,
            Type::DECIMAL => ($column[Type::DOUBLE] ?? false) ? 'DOUBLE' : 'FLOAT',
            Type::BOOL => $this->platform->useBitString(),
            Type::INTEGER => (self::$column_type[$length] ?? $int)
                . (($column[Type::UNSIGNED] ?? false) ? $this->platform->useUnsigned() : ''),
            default => 'VARCHAR(' . self::$defaultTextLength . ')',
        };
    }

    private function _drop_sql(string $table): string
    {
        return "DROP TABLE IF EXISTS `$table`;";
    }
}